﻿using System;
using System.Collections;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Reflection;


using BizElements.Web;
using BizElements.BusinessLayer;

namespace BizElements.Web
{
    /// <summary>
    /// Utility class for common Web operations.
    /// </summary>
    public static class WebUtil
    {
        #region Disable browser page cache.

        /// <summary>
        /// Prevents page cacheing on browsers.
        /// </summary>
        public static void DontCacheCurrentPageOnBrowser()
        {
            Page page = (Page)HttpContext.Current.Handler;
                        
            PropertyInfo httpCachePolicyProperty = typeof(HttpResponse).GetProperty("Cache");
            if (httpCachePolicyProperty != null)
            {
                // .NET 3.5 uses new HttpCachePolicy type to set caching options.                
                // Reflection is used to ensure that the BizElements.Web is compatibile with older runtimes.

                // page.Response.Cache.SetAllowResponseInBrowserHistory(false);
                object httpCachePolicy = httpCachePolicyProperty.GetValue(page.Response, null);
                Assembly asm = httpCachePolicy.GetType().Assembly;
                ReflectionUtil.InvokeMethod(httpCachePolicy, "SetAllowResponseInBrowserHistory", new object[] { false });

                // page.Response.Cache.SetCacheability(HttpCacheability.NoCache)
                Type httpCacheabilityEnum = asm.GetType("System.Web.HttpCacheability");
                object noCache = Enum.Parse(httpCacheabilityEnum, "NoCache");
                ReflectionUtil.InvokeMethod(httpCachePolicy, "SetCacheability", new object[] { noCache });
            }
            else
            {
                // Older .NET versions.
                page.Response.ExpiresAbsolute = Time.Now.Subtract(new TimeSpan(1, 0, 0, 0));
                page.Response.Expires = 0;
                page.Response.CacheControl = "no-cache";
            }
        }

        #endregion

        #region Get+SetSessionVariable.

        /// <summary>
        /// Sets or removes session variable.
        /// </summary>
        /// <param name="name">The key name of the session value.</param>
        /// <param name="value">Value of null-reference. If null then the variable is removed from the session.</param>
        private static void SetSessionVariable(string name, object value)
        {
            if (value != null)
                HttpContext.Current.Session[name] = value;
            else
                HttpContext.Current.Session.Remove(name);
        }

        /// <summary>
        /// Sets or removes session form variable.
        /// </summary>
        /// <param name="form">The type of the Web-component which uses the variable. Required to generate a unique session key.</param>
        /// <param name="key">The key of session form variable.</param>
        /// <param name="value">Value or null-reference. If null then the variable is removed from the session.</param>
        public static void SetSessionVariable(Type form, string key, object value)
        {
            SetSessionVariable(GetSessionVariableName(form, key), value);
        }

        /// <summary>
        /// Gets object stored in session form variable.
        /// </summary>
        /// <typeparam name="T">Type of the object to get.</typeparam>
        /// <param name="form">The type of the form which uses the variable. Required to generate a unique session key.</param>
        /// <param name="key">The key of session form variable.</param>
        /// <returns>Object or null-reference if not found.</returns>
        public static T GetSessionObject<T>(Type form, string key)
            where T : class
        {
            return HttpContext.Current.Session[GetSessionVariableName(form, key)] as T;
        }

        /// <summary>
        /// Gets value stored in session form variable.
        /// </summary>
        /// <typeparam name="T">Type of the value to get.</typeparam>
        /// <param name="form">The type of the form which uses the variable. Required to generate a unique session key.</param>
        /// <param name="key">The key of session form variable.</param>
        /// <returns>Value or null if not found.</returns>
        public static T? GetSessionValue<T>(Type form, string key)
            where T : struct
        {
            object value = HttpContext.Current.Session[GetSessionVariableName(form, key)];
            if (value != null)
                return (T)value;
            else
                return null;
        }

        /// <summary>
        /// Removes all form session variables which were registered by <see cref="SetSessionVariable(Type, string, object)"/> method.
        /// </summary>
        /// <param name="form">The type of the form which uses the variable.</param>
        public static void RemoveSessionVariables(Type form)
        {
            System.Web.SessionState.HttpSessionState session = HttpContext.Current.Session;
            string varPrefix = GetSessionVariablePrefix(form);
            foreach (string sessionVariableName in session.Keys)
            {
                bool belongsToForm = sessionVariableName.StartsWith(varPrefix);
                if (belongsToForm)
                    session.Remove(sessionVariableName);
            }
        }

        private static string GetSessionVariablePrefix(Type form)
        {
            return "WebUtil." + form.FullName + ".";
        }

        private static string GetSessionVariableName(Type form, string key)
        {
            return GetSessionVariablePrefix(form) + key;
        }

        #endregion

        #region CheckControlRules.

        /// <summary>
        /// Enables/disables controls based on the provided contitions/rules.
        /// </summary>
        /// <param name="rules">Rules which are displayed in tooltip if broken.</param>
        /// <param name="controls">Controls to check.</param>
        public static void CheckControlRules(BusinessRuleCollection rules, params WebControl[] controls)
        {
            if (rules.HasBrokenRules)
            {
                string tooltip = rules.GetBrokenRules().ToString();
                foreach (WebControl ctl in controls)
                {
                    // Enabled=false may not work for template buttons - depends on template used.
                    if (ctl is TemplateButton || ctl is TemplatePopupButton)
                    {
                        ctl.Visible = false;
                    }
                    else
                    {
                        ctl.Enabled = false;
                        if (!string.IsNullOrEmpty(tooltip))
                            ctl.ToolTip = tooltip;
                    }
                }
            }
            else
            {
                foreach (WebControl ctl in controls)
                    ctl.Enabled = true;
            }
        }

        /// <summary>
        /// Enables/disables controls based on the provided contitions/rules.
        /// </summary>
        /// <param name="rules">Rules which are displayed in tooltip if broken.</param>
        /// <param name="formatter">The <b>IMessageFormatter</b> that formattes the values contained in the descriptions of the rules.</param>
        /// <param name="controls">Controls to check.</param>
        public static void CheckControlRules(BusinessRuleCollection rules, IMessageFormatter formatter, params WebControl[] controls)
        {
            if (rules.HasBrokenRules)
            {
                string tooltip = rules.GetBrokenRules().ToString(formatter);
                foreach (WebControl ctl in controls)
                {
                    // Enabled=false may not work for template buttons - depends on template used.
                    if (ctl is TemplateButton || ctl is TemplatePopupButton)
                    {
                        ctl.Visible = false;
                    }
                    else
                    {
                        ctl.Enabled = false;
                        if (!string.IsNullOrEmpty(tooltip))
                            ctl.ToolTip = tooltip;
                    }
                }
            }
            else
            {
                foreach (WebControl ctl in controls)
                    ctl.Enabled = true;
            }
        }

        #endregion

        #region Query string.

        /// <summary>
        /// Appends new query string parameter to given URL.
        /// </summary>
        /// <param name="url">URL to which new key-value pair is appended.</param>
        /// <param name="key">Parameter name.</param>
        /// <param name="value">Parameter value.</param>
        public static void AppendParamToUrl(ref string url, string key, string value)
        {
            url = url + GetNextUrlOperator(url) + key + "=" + value;
        }

        /// <summary>
        /// Gets next URL concatenation operator, ie. question mark or ampersand.
        /// </summary>
        /// <param name="url">URL.</param>
        /// <returns>Question mark if URL doesn't contain a query string, otherwise ampersand.</returns>
        public static string GetNextUrlOperator(string url)
        {
            string op = url.Contains("?") ? "&" : "?";
            return op;
        }

        /// <summary>
        /// Sets the specified query string variable. Appends it to the end of the URL if it doesn't already exist.
        /// </summary>
        /// <param name="url">URL which contains the specified query variable or to which it will be appended. <b>null</b> and <see cref="String.Empty"/> are not valid values.</param>
        /// <param name="key">The name of the quiery string variable. <b>null</b> and <see cref="String.Empty"/> are not valid values.</param>
        /// <param name="value">The value of the query string variable (automatically URL encoded). <b>null</b> and <see cref="String.Empty"/> are not valid values.</param>
        /// <returns>URL with changed or appended variable value.</returns>
        public static string SetUrlParam(string url, string key, string value)
        {
            if (string.IsNullOrEmpty(url)) throw new ArgumentException("Empty string or null is not a valid URL value.", "url");
            if (string.IsNullOrEmpty(key)) throw new ArgumentException("Empty string or null is not a valid query parameter key value.", "key");
            if (string.IsNullOrEmpty(value)) throw new ArgumentException("Empty string or null is not a valid parameter value.", "value");
            
            value = HttpUtility.UrlEncode(value);

            string keyEquals = key + "=";
            bool isFirstVar = url.Contains("?" + keyEquals);
            bool isIntermedVar = (isFirstVar) ? false : url.Contains("&" + keyEquals);
            bool overwriteExisting = (isFirstVar || isIntermedVar);
            if (overwriteExisting)
            {
                string op = (isFirstVar) ? "?" : "&";
                string opKeyEquals = op + keyEquals;
                int beginsAt = url.IndexOf(opKeyEquals) + opKeyEquals.Length;

                int oldValueEndsAt = url.IndexOf("&", beginsAt + 1);
                bool isLastVariable = (oldValueEndsAt < 0);
                if (isLastVariable)
                    oldValueEndsAt = url.Length - 1;

                int oldValueLen = oldValueEndsAt - beginsAt + 1;
                url = url.Remove(beginsAt, oldValueLen);
                if (isLastVariable)
                    url = url.Insert(beginsAt, value);
                else
                    url = url.Insert(beginsAt, value + "&");
            }
            else
            {
                url += GetNextUrlOperator(url) + keyEquals + value;
            }

            return url;
        }

        #endregion

        #region RecursivelyFindControl.

        /// <summary>Search child control hierarchy for the first occurrence of server control with the specified ID. Uses breath-first algorithm.</summary>
        /// <param name="parent">Parent control.</param>
        /// <param name="childControlId">Control ID.</param>
        /// <returns>A control if fount; otherwise <b>null</b>.</returns>
        public static Control RecursivelyFindControl(Control parent, string childControlId)
        {
            // Breadth first.
            foreach (Control ctl in parent.Controls)
            {
                if (ctl.ID == childControlId)
                    return ctl;
            }

            foreach (Control ctl in parent.Controls)
            {
                Control match = RecursivelyFindControl(ctl, childControlId);
                if (match != null)
                    return match;
            }

            return null;
        }

        #endregion

        internal static bool IsRelativeUrl(string url)
        {
            bool isRelativeUrl = url.StartsWith("~/");
            return isRelativeUrl;
        }

        internal static string ResolveUrl(string relativeOrAbsoluteUrl)
        {
            if (string.IsNullOrEmpty(relativeOrAbsoluteUrl))
                return relativeOrAbsoluteUrl;

            string absoluteUrl;
            Control page = HttpContext.Current.Handler as Control;
            if (page != null)
                absoluteUrl = page.ResolveUrl(relativeOrAbsoluteUrl);
            else if (IsRelativeUrl(relativeOrAbsoluteUrl))
                absoluteUrl = HttpContext.Current.Request.ApplicationPath + relativeOrAbsoluteUrl.Substring(1, relativeOrAbsoluteUrl.Length - 1);
            else
                absoluteUrl = relativeOrAbsoluteUrl;

            return absoluteUrl;
        }
    }
}